package src

import (
	"strings"
	"sync"
	"time"

	"gitee.com/haifengat/goctp/v2"
	zd "gitee.com/haifengat/zorm-dm/v2"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

func startTrade() {
	trd = goctp.NewTradePro()
	logrus.Infof("trade:%s user:%s,pwd:%s,broker:%s,appid:%s,authcode:%s", tradeFront, investorID, password, brokerID, appID, authCode)

	trd.OnRtnInstrumentStatus = func(pInstrumentStatus *goctp.CThostFtdcInstrumentStatusField) {
		instrumentStatus[pInstrumentStatus.InstrumentID.String()] = pInstrumentStatus.InstrumentStatus
	}

	trd.OnOrder = func(pOrder *goctp.CThostFtdcOrderField) {
		sseOrder(new(OrderField).FromCTP(*pOrder))
	}
	trd.OnTrade = func(pTrade *goctp.CThostFtdcTradeField) {
		sseTrade(new(TradeField).FromCTP(*pTrade))
	}
	for k := range instrumentStatus {
		delete(instrumentStatus, k)
	}

	logInfo, info := trd.Start(goctp.LoginConfig{
		Front:    tradeFront,
		Broker:   brokerID,
		UserID:   investorID,
		Password: password,
		AppID:    appID,
		AuthCode: authCode,
	})

	if info.ErrorID != 0 {
		logrus.Fatalf("ctp登录报错: {%+v}", info)
		return
	}

	logrus.Infof("交易登录成功 %+v, 行情登录...", logInfo)
	if err := onLogin(&logInfo); err != nil {
		logrus.Fatalf("ctp登录后初始化数据失败: {%+v}", err)
		return
	}
	save2DB() // 保存交易数据到数据库

	startQuote()

	// 直至交易结束, 退出
	now := time.Now().Local()
	curDay := now.Format("20060102")
	if tm, _ := time.ParseInLocation("20060102150405", curDay+"023000", time.Local); now.Before(tm) { // 02:30 夜盘收盘
		logrus.Infof("%s 释放接口", tm)
		time.Sleep(time.Until(tm))
	} else if tm, _ := time.ParseInLocation("20060102150405", curDay+"151600", time.Local); now.Before(tm) { // 15:16 收盘时间
		logrus.Infof("%s 释放接口", tm)
		time.Sleep(time.Until(tm))
	} else if tm, _ := time.ParseInLocation("20060102150405", curDay+"203000", time.Local); now.Before(tm) { // 20:30 夜盘收盘
		logrus.Infof("夜盘 %s 前不要启动", tm)
	} else { // 夜盘->第2天02:30
		tm, _ := time.ParseInLocation("20060102150405", curDay+"023000", time.Local)
		tm = tm.AddDate(0, 0, 1) // 第2天 02:30
		logrus.Infof("%s 释放接口", tm)
		time.Sleep(time.Until(tm))
	}
	save2DB() // 保存交易数据到数据库
	logrus.Info("Quote release ...")
	md.Release() // 行情端释放
	logrus.Info("Trade release ...")
	trd.Release() // 交易端释放
}

func onLogin(login *goctp.CThostFtdcRspUserLoginField) (errRtn error) {
	// 初始化
	instLastMin = sync.Map{}
	tradingDay = login.TradingDay.String()
	if tmp, err := zd.SelectRow[Calendar](ctxDAO, "Max(TradingDay) as actionday", map[string]any{"TradingDay": map[string]any{"<": tradingDay}}); err == nil {
		actionDay = tmp.(string)
	} else {
		errRtn = errors.Wrap(err, "查询actionday失败")
		return
	}
	// minPushed = sync.Map{}
	// mapInstrumentStatus = sync.Map{} // 会导致收不到行情：登录事件时交易状态已更新
	// 初始化 actionday
	preDay, _ := rdb.HGet(ctxRedis, "tradingday", "curday").Result()
	if strings.Compare(preDay, tradingDay) != 0 {
		cntTicks = 0
		logrus.Infof("清仓数据: %s, 交易日: %s", preDay, tradingDay)
		rdb.FlushAll(ctxRedis)
		rdb.HSet(ctxRedis, "tradingday", "curday", tradingDay)
		// 保存合约信息
		insts := make([]map[string]any, 0)
		for k, v := range trd.Instruments {
			info := InstrumentField{
				InstrumentID:      k,
				InstrumentName:    v.InstrumentName.String(),
				ProductID:         v.ProductID.String(),
				ExchangeID:        ExchangeIDType(v.ExchangeID.String()),
				PriceTick:         float64(v.PriceTick),
				VolumeMultiple:    int(v.VolumeMultiple),
				UnderlyingInstrID: v.UnderlyingInstrID.String(),
				OptionsType:       v.OptionsType,
				OpenDate:          v.OpenDate.String(),
				ExpireDate:        v.ExpireDate.String(),
				ProductClass:      v.ProductClass,
			}
			info.InsertTime = DateTimeType(time.Now().Local().Format(time.DateTime))
			info.UpdateTime = DateTimeType(time.Now().Local().Format(time.DateTime))
			insts = append(insts, zd.StructToMap(ctxDAO, info, false, true))
			if len(insts) == 1000 {
				zd.UpdateAndInsert[InstrumentField](ctxDAO, insts...)
				insts = make([]map[string]any, 0)
			}
		}
		zd.UpdateAndInsert[InstrumentField](ctxDAO, insts...)
	} else if n, _ := zd.SelectCount[InstrumentField](ctxDAO, nil); n == 0 {
		// 保存合约信息
		insts := make([]map[string]any, 0)
		for k, v := range trd.Instruments {
			info := InstrumentField{
				InstrumentID:      k,
				InstrumentName:    v.InstrumentName.String(),
				ProductID:         v.ProductID.String(),
				ExchangeID:        ExchangeIDType(v.ExchangeID.String()),
				PriceTick:         float64(v.PriceTick),
				VolumeMultiple:    int(v.VolumeMultiple),
				UnderlyingInstrID: v.UnderlyingInstrID.String(),
				OptionsType:       v.OptionsType,
				OpenDate:          v.OpenDate.String(),
				ExpireDate:        v.ExpireDate.String(),
				ProductClass:      v.ProductClass,
			}
			info.InsertTime = DateTimeType(time.Now().Local().Format(time.DateTime))
			info.UpdateTime = DateTimeType(time.Now().Local().Format(time.DateTime))
			insts = append(insts, zd.StructToMap(ctxDAO, info, false, true))
			if len(insts) == 1000 {
				zd.UpdateAndInsert[InstrumentField](ctxDAO, insts...)
				insts = make([]map[string]any, 0)
			}
		}
		zd.UpdateAndInsert[InstrumentField](ctxDAO, insts...)
	}
	return
}
